//
//  write.cpp
//  
//
//  Created by Nikita Luzyanin on 30.11.13.
//
//

#include <iostream>
#include <fstream>
#include <iomanip>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include "functions.cpp"
using namespace std;

int writeRaidType0(char file[2048], int lengthOfFile, int lengthOfMetaBlock, int numberOfBlockToWrite, int numberOfDisks, int sizeOfBlock)
{
    fstream WTD;
    int lengthForEachBlock = lengthOfFile / numberOfDisks;
    int numberOfByteToWrite = 0;
    char diskName[10];
    char diskNumber[10];
    int positionToWriteFrom = lengthOfMetaBlock + numberOfBlockToWrite * sizeOfBlock + 1;
    
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisks; numberOfDisk++) {
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        WTD.open(diskName);
        WTD.seekp(positionToWriteFrom);
        
        int lengthForBlock = lengthForEachBlock;
        if (numberOfDisk == (numberOfDisks - 1)) {
            lengthForBlock = lengthOfFile - lengthForEachBlock * numberOfDisk;
        }
        WTD << lengthForBlock;
        WTD << "\n";
        for (int bytesWritten = 0; bytesWritten < lengthForBlock; bytesWritten++) {
            WTD << file[numberOfByteToWrite];
            numberOfByteToWrite ++;
        }
        WTD.close();
    }
    
    return 0;
}

int writeRaidType1(char file[2048], int lengthOfFile, int lengthOfMetaBlock, int numberOfBlockToWrite, int numberOfDisks, int sizeOfBlock)
{
    fstream WTD;
    char diskName[10];
    char diskNumber[10];
    int positionToWriteFrom = lengthOfMetaBlock + numberOfBlockToWrite * sizeOfBlock + 1;
    
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisks; numberOfDisk++) {
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        WTD.open(diskName);
        WTD.seekp(positionToWriteFrom);
        
        WTD << lengthOfFile;
        WTD << "\n";
        for (int bytesWritten = 0; bytesWritten < lengthOfFile; bytesWritten++) {
            WTD << file[bytesWritten];
        }
        WTD.close();
    }
    return 0;
}

int writeRaidType3(char file[2048], int lengthOfFile, int lengthOfMetaBlock, int numberOfBlockToWrite, int numberOfDisks, int sizeOfBlock)
{
    fstream WTD;
    char diskName[10];
    char diskNumber[10];
    int disksForData = numberOfDisks - 1;
    char dataForEachDisk[numberOfDisks][2048];
    int lengthForEachDisk[numberOfDisks];
    for (int i = 0; i < numberOfDisks; i ++) {
        lengthForEachDisk[i] = 0;
    }
    
    for (int bytesWritten = 0; bytesWritten < lengthOfFile; bytesWritten ++) {
        int helper = bytesWritten / disksForData;
        int diskToWrite = bytesWritten - helper * disksForData;
        dataForEachDisk[diskToWrite][lengthForEachDisk[diskToWrite]] = file[bytesWritten];
        lengthForEachDisk[diskToWrite] ++;
    }
    
    /*for (int numberOfDisk = 1; numberOfDisk < numberOfDisks; numberOfDisk ++) {
        if (lengthForEachDisk[0] > lengthForEachDisk[numberOfDisk]) {
            dataForEachDisk[numberOfDisk][lengthForEachDisk[numberOfDisk]] = '0';
        }
    }*/
    lengthForEachDisk[disksForData] = lengthForEachDisk[0];

    makeParity(dataForEachDisk[disksForData], dataForEachDisk, lengthForEachDisk[0], numberOfDisks, disksForData);
    
    int positionToWriteFrom = lengthOfMetaBlock + numberOfBlockToWrite * sizeOfBlock + 1;
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisks; numberOfDisk ++) {
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        WTD.open(diskName);
        WTD.seekp(positionToWriteFrom);
        
        WTD << lengthForEachDisk[numberOfDisk];
        WTD << "\n";
        for (int bytesWritten = 0; bytesWritten < lengthForEachDisk[numberOfDisk]; bytesWritten++) {
            WTD << dataForEachDisk[numberOfDisk][bytesWritten];
        }
        WTD.close();
    }
    
    return 0;
}

int writeRaidType4(char file[2048], int lengthOfFile, int lengthOfMetaBlock, int numberOfBlockToWrite, int numberOfDisks, int sizeOfBlock)
{
    fstream WTD;
    char diskName[10];
    char diskNumber[10];
    int disksForData = numberOfDisks - 1;
    char dataForEachDisk[numberOfDisks][2048];
    int commonLengthForEachDisk = lengthOfFile / disksForData;
    int lengthForEachDisk[numberOfDisks];
    int bytesRead = 0;
    
    for (int numberOfDisk = 0; numberOfDisk < disksForData; numberOfDisk++) {
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        
        lengthForEachDisk[numberOfDisk] = commonLengthForEachDisk;
        if (numberOfDisk == (disksForData - 1)) {
            lengthForEachDisk[numberOfDisk] = lengthOfFile - commonLengthForEachDisk * numberOfDisk;
        }
        
        for (int bytesWritten = 0; bytesWritten < lengthForEachDisk[numberOfDisk]; bytesWritten++) {
            dataForEachDisk[numberOfDisk][bytesWritten] = file[bytesRead];
            bytesRead ++;
        }
    }
    
    lengthForEachDisk[disksForData] = lengthForEachDisk[disksForData - 1];
    makeParity(dataForEachDisk[disksForData], dataForEachDisk, lengthForEachDisk[disksForData], numberOfDisks, disksForData);
    
    int positionToWriteFrom = lengthOfMetaBlock + numberOfBlockToWrite * sizeOfBlock + 1;
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisks; numberOfDisk++) {
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        WTD.open(diskName);
        WTD.seekp(positionToWriteFrom);
        WTD << lengthForEachDisk[numberOfDisk];
        WTD << "\n";
        for (int bytesWritten = 0; bytesWritten < lengthForEachDisk[numberOfDisk]; bytesWritten++) {
            WTD << dataForEachDisk[numberOfDisk][bytesWritten];
        }
        WTD.close();
    }
    
    return 0;
}

int writeRaidType5(char file[2048], int lengthOfFile, int lengthOfMetaBlock, int numberOfBlockToWrite, int numberOfDisks, int sizeOfBlock)
{
    fstream WTD;
    char diskName[10];
    char diskNumber[10];
    char dataForEachDisk[numberOfDisks][2048];
    int commonLengthForEachDisk = lengthOfFile / (numberOfDisks - 1);
    int lengthForEachDisk[numberOfDisks];
    int bytesRead = 0;
    
    int helper = numberOfBlockToWrite / numberOfDisks;
    int diskWithParity = numberOfBlockToWrite - helper * (numberOfDisks - 1);
    int lastDisk = numberOfDisks - 1;
    if (diskWithParity == (numberOfDisks - 1)) {
        lastDisk = numberOfDisks - 2;
    }
    
    
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisks; numberOfDisk++) {
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        
        if (numberOfDisk != diskWithParity) {
            lengthForEachDisk[numberOfDisk] = commonLengthForEachDisk;
            if (numberOfDisk == lastDisk) {
                lengthForEachDisk[numberOfDisk] = lengthOfFile - commonLengthForEachDisk * (numberOfDisk - 1);
            }
            for (int bytesWritten = 0; bytesWritten < lengthForEachDisk[numberOfDisk]; bytesWritten++) {
                dataForEachDisk[numberOfDisk][bytesWritten] = file[bytesRead];
                bytesRead ++;
            }
        }
    }
    
    lengthForEachDisk[diskWithParity] = lengthForEachDisk[lastDisk];
    makeParity(dataForEachDisk[diskWithParity], dataForEachDisk, lengthForEachDisk[diskWithParity], numberOfDisks, diskWithParity);
    
    int positionToWriteFrom = lengthOfMetaBlock + numberOfBlockToWrite * sizeOfBlock + 1;
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisks; numberOfDisk++) {
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        WTD.open(diskName);
        WTD.seekp(positionToWriteFrom);
        WTD << lengthForEachDisk[numberOfDisk];
        WTD << "\n";
        for (int bytesWritten = 0; bytesWritten < lengthForEachDisk[numberOfDisk]; bytesWritten++) {
            WTD << dataForEachDisk[numberOfDisk][bytesWritten];
        }
        WTD.close();
    }
    
    return 0;
}

int writeRaidType01(char file[2048], int lengthOfFile, int lengthOfMetaBlock, int numberOfBlockToWrite, int numberOfDisks, int sizeOfBlock)
{
    fstream WTD;
    int numberOfDisksFor0Type = numberOfDisks / 2;
    int numberOfDisksFor1Type = numberOfDisks / 2;
    int lengthForEachBlock = lengthOfFile / numberOfDisksFor0Type;
    int numberOfByteToWrite = 0;
    char diskName[10];
    char diskNumber[10];
    int positionToWriteFrom = lengthOfMetaBlock + numberOfBlockToWrite * sizeOfBlock + 1;
    
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisksFor0Type; numberOfDisk++) {
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        WTD.open(diskName);
        WTD.seekp(positionToWriteFrom);
        
        int lengthForBlock = lengthForEachBlock;
        if (numberOfDisk == (numberOfDisksFor0Type - 1)) {
            lengthForBlock = lengthOfFile - lengthForEachBlock * numberOfDisk;
        }
        WTD << lengthForBlock;
        WTD << "\n";
        for (int bytesWritten = 0; bytesWritten < lengthForBlock; bytesWritten++) {
            WTD << file[numberOfByteToWrite];
            numberOfByteToWrite ++;
        }
        WTD.close();
        
        char dstDiskName[5];
        sprintf(diskNumber, "%i", numberOfDisk + numberOfDisksFor0Type);
        strcpy(dstDiskName, "disk");
        strcat(dstDiskName, diskNumber);
        
        copyBlock(diskName, dstDiskName, positionToWriteFrom);
    }
    
    return 0;
}

int writeRaidType10(char file[2048], int lengthOfFile, int lengthOfMetaBlock, int numberOfBlockToWrite, int numberOfDisks, int sizeOfBlock)
{
    fstream WTD;
    int numberOfDisksFor0Type = numberOfDisks / 2;
    int numberOfDisksFor1Type = numberOfDisks / 2;
    int lengthForEachBlock = lengthOfFile / numberOfDisksFor0Type;
    int numberOfByteToWrite = 0;
    char diskName[10];
    char diskNumber[10];
    int positionToWriteFrom = lengthOfMetaBlock + numberOfBlockToWrite * sizeOfBlock + 1;
    
    int blocksWritten;
    for (int i = 0; i < numberOfDisksFor1Type; i ++) {
        blocksWritten = 0;
        numberOfByteToWrite = 0;
        for (int numberOfDisk = i; numberOfDisk < numberOfDisks; numberOfDisk = numberOfDisk + numberOfDisksFor0Type) {
            int lengthForBlock = lengthForEachBlock;
            if (blocksWritten == (numberOfDisksFor0Type - 1)) {
                lengthForBlock = lengthOfFile - lengthForEachBlock * blocksWritten;
            }
            blocksWritten ++;
            
            sprintf(diskNumber, "%i", numberOfDisk);
            strcpy(diskName, "disk");
            strcat(diskName, diskNumber);
            WTD.open(diskName);
            WTD.seekp(positionToWriteFrom);
            
            WTD << lengthForBlock;
            WTD << "\n";
            for (int bytesWritten = 0; bytesWritten < lengthForBlock; bytesWritten++) {
                WTD << file[numberOfByteToWrite];
                numberOfByteToWrite ++;
            }
            WTD.close();
        }
    }
    
    return 0;
}

int main(int argc, char* argv[])
{
    int numberOfDisks, numberOfBlocks, sizeOfBlock, lengthOfMetaBlock;
    char raidType[2];
    char missingDisksNames[5][100];
    int numberOfDisksMissing;
    if (checkDisks(numberOfDisks, numberOfBlocks, sizeOfBlock, raidType, lengthOfMetaBlock, missingDisksNames, numberOfDisksMissing) != 0) {
        return 1;
    } else if (numberOfDisksMissing > 0) {
        cout << numberOfDisksMissing << " disks are missing\n";
        return 1;
    }
    
    char pathToFile[1000];
    strcpy(pathToFile, argv[2]);
    if (checkFile(pathToFile) != 0) {
        return 1;
    }
    
    fstream FS;
    char file[2048], ch;
    int lengthOfFile = 0;
    FS.open(pathToFile);
    ch = FS.get();
    while (!FS.eof()) {
        file[lengthOfFile] = ch;
        lengthOfFile ++;
        ch = FS.get();
    }
    FS.close();
    
    int numberOfBlockToWrite = atoi(argv[1]);
    if ((numberOfBlockToWrite < 0)||(numberOfBlockToWrite > numberOfBlocks)) {
        cout << "No block with such number\n";
        return 1;
    }
    
    if (lengthOfFile > sizeOfBlock - (lengthOfMetaBlock +1)) {
        cout << "File is too big\n";
        return 1;
    }
    
    char diskName[10];
    char diskNumber[10];
    int positionToClearFrom = lengthOfMetaBlock + numberOfBlockToWrite * sizeOfBlock + 1;
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisks; numberOfDisk ++) {
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        clearBlockOnDisk(diskName, positionToClearFrom ,sizeOfBlock);
    }

    bool completed = true;
    if (strcmp(raidType, "0") == 0) {
        if (writeRaidType0(file, lengthOfFile, lengthOfMetaBlock, numberOfBlockToWrite, numberOfDisks, sizeOfBlock) != 0) {
            completed = false;
        }
    } else if (strcmp(raidType, "1") == 0) {
        if (writeRaidType1(file, lengthOfFile, lengthOfMetaBlock, numberOfBlockToWrite, numberOfDisks, sizeOfBlock) != 0) {
            completed = false;
        }
    } else if (strcmp(raidType, "3") == 0) {
        if (writeRaidType3(file, lengthOfFile, lengthOfMetaBlock, numberOfBlockToWrite, numberOfDisks, sizeOfBlock) != 0) {
            completed = false;
        }
    } else if (strcmp(raidType, "4") == 0) {
        if (writeRaidType4(file, lengthOfFile, lengthOfMetaBlock, numberOfBlockToWrite, numberOfDisks, sizeOfBlock) != 0) {
            completed = false;
        }
    } else if (strcmp(raidType, "5") == 0) {
        if (writeRaidType5(file, lengthOfFile, lengthOfMetaBlock, numberOfBlockToWrite, numberOfDisks, sizeOfBlock) != 0) {
            completed = false;
        }
    } else if ((raidType[0] == '0')&&(raidType[1] == '1')) {
        if (writeRaidType01(file, lengthOfFile, lengthOfMetaBlock, numberOfBlockToWrite, numberOfDisks, sizeOfBlock) != 0) {
            completed = false;
        }
    } else if ((raidType[0] == '1')&&(raidType[1] == '0')) {
        if (writeRaidType10(file, lengthOfFile, lengthOfMetaBlock, numberOfBlockToWrite, numberOfDisks, sizeOfBlock) != 0) {
            completed = false;
        }
    }
    
    if (completed == false) {
        cout << "Mistake while writing\n";
        return 1;
    }
    return 0;
}